{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 数据处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 安装类库\n",
    "# !mkdir /home/aistudio/external-libraries\n",
    "# !pip install imgaug -t /home/aistudio/external-libraries\n",
    "import sys\n",
    "sys.path.append('/home/aistudio/external-libraries')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "image shape: (32, 32, 3)\n",
      "label value: horse\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 216x216 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import paddle\n",
    "import numpy as np\n",
    "from PIL import Image\n",
    "import matplotlib.pyplot as plt\n",
    "import imgaug as ia\n",
    "import imgaug.augmenters as iaa\n",
    "\n",
    "# 读取数据\n",
    "reader = paddle.batch(\n",
    "    paddle.dataset.cifar.train10(),\n",
    "    batch_size=8) # 数据集读取器\n",
    "data = next(reader()) # 读取数据\n",
    "index = 4 # 批次索引\n",
    "\n",
    "# 读取图像\n",
    "image = np.array([x[0] for x in data]).astype(np.float32) # 读取图像数据，数据类型为float32\n",
    "image = image * 255 # 从[0,1]转换到[0,255]\n",
    "image = image[index].reshape((3, 32, 32)).transpose((1, 2, 0)).astype(np.uint8) # 数据格式从CHW转换为HWC，数据类型转换为uint8\n",
    "print('image shape:', image.shape)\n",
    "\n",
    "# 图像增强\n",
    "# sometimes = lambda aug: iaa.Sometimes(0.5, aug) # 随机进行图像增强\n",
    "# seq = iaa.Sequential([\n",
    "#     sometimes(iaa.CropAndPad(px=(-4, 4))),      # 随机裁剪填充像素\n",
    "#     iaa.Fliplr(0.5)])                           # 随机进行水平翻转\n",
    "# image = seq(image=image)\n",
    "\n",
    "# 读取标签\n",
    "label = np.array([x[1] for x in data]).astype(np.int64) # 读取标签数据，数据类型为int64\n",
    "vlist = [\"airplane\", \"automobile\", \"bird\", \"cat\", \"deer\", \"dog\", \"frog\", \"horse\", \"ship\", \"truck\"] # 标签名称列表\n",
    "print('label value:', vlist[label[index]])\n",
    "\n",
    "# 显示图像\n",
    "image = Image.fromarray(image)   # 转换图像格式\n",
    "image.save('./work/out/img.png') # 保存读取图像\n",
    "plt.figure(figsize=(3, 3))       # 设置显示大小\n",
    "plt.imshow(image)                # 设置显示图像\n",
    "plt.show()                       # 显示图像文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "train_data: image shape (128, 3, 32, 32), label shape:(128, 1)\n",
      "valid_data: image shape (128, 3, 32, 32), label shape:(128, 1)\n"
     ]
    }
   ],
   "source": [
    "import paddle\n",
    "import numpy as np\n",
    "import imgaug as ia\n",
    "import imgaug.augmenters as iaa\n",
    "\n",
    "# 训练数据增强\n",
    "def train_augment(images):\n",
    "    # 转换格式\n",
    "    images = images * 255 # 从[0,1]转换到[0,255]\n",
    "    images = images.transpose((0, 2, 3, 1)).astype(np.uint8) # 数据格式从BCHW转换为BHWC，数据类型转换为uint8\n",
    "    \n",
    "    # 增强图像\n",
    "    sometimes = lambda aug: iaa.Sometimes(0.5, aug) # 随机进行图像增强\n",
    "    seq = iaa.Sequential([\n",
    "        sometimes(iaa.CropAndPad(px=(-4, 4))),      # 随机裁剪填充像素\n",
    "        iaa.Fliplr(0.5)])                           # 随机进行水平翻转\n",
    "    images = seq(images=images)\n",
    "    \n",
    "    # 减去均值\n",
    "    mean = np.array([0.4914, 0.4822, 0.4465]).reshape((1, 1, 1, -1)) # cifar数据集通道平均值\n",
    "    stdv = np.array([0.2471, 0.2435, 0.2616]).reshape((1, 1, 1, -1)) # cifar数据集通道标准差\n",
    "    \n",
    "    images = (images/255.0 - mean) / stdv # 对图像进行归一化\n",
    "    images = images.transpose((0, 3, 1, 2)).astype(np.float32) # 数据格式从BHWC转换为BCHW，数据类型转换为float32\n",
    "    \n",
    "    return images\n",
    "\n",
    "# 验证数据增强\n",
    "def valid_augment(images):\n",
    "    # 转换格式\n",
    "    images = images * 255 # 从[0,1]转换到[0,255]\n",
    "    images = images.transpose((0, 2, 3, 1)).astype(np.uint8) # 数据格式从BCHW转换为BHWC，数据类型转换为uint8\n",
    "    \n",
    "    # 减去均值\n",
    "    mean = np.array([0.4914, 0.4822, 0.4465]).reshape((1, 1, 1, -1)) # cifar数据集通道平均值\n",
    "    stdv = np.array([0.2471, 0.2435, 0.2616]).reshape((1, 1, 1, -1)) # cifar数据集通道标准差\n",
    "    \n",
    "    images = (images/255.0 - mean) / stdv # 对图像进行归一化\n",
    "    images = images.transpose((0, 3, 1, 2)).astype(np.float32) # 数据格式从BHWC转换为BCHW，数据类型转换为float32\n",
    "    \n",
    "    return images\n",
    "\n",
    "# 读取训练数据\n",
    "train_reader = paddle.batch(\n",
    "    paddle.reader.shuffle(paddle.dataset.cifar.train10(), buf_size=50000),\n",
    "    batch_size=128) # 构造数据读取器\n",
    "train_data = next(train_reader()) # 读取训练数据\n",
    "\n",
    "train_image = np.array([x[0] for x in train_data]).reshape((-1, 3, 32, 32)).astype(np.float32) # 读取训练图像\n",
    "train_image = train_augment(train_image)                                                       # 训练图像增强\n",
    "train_label = np.array([x[1] for x in train_data]).reshape((-1, 1)).astype(np.int64)           # 读取训练标签\n",
    "print('train_data: image shape {}, label shape:{}'.format(train_image.shape, train_label.shape))\n",
    "\n",
    "# 读取验证数据\n",
    "valid_reader = paddle.batch(\n",
    "    paddle.dataset.cifar.test10(),\n",
    "    batch_size=128) # 构造数据读取器\n",
    "valid_data = next(valid_reader()) # 读取验证数据\n",
    "\n",
    "valid_image = np.array([x[0] for x in valid_data]).reshape((-1, 3, 32, 32)).astype(np.float32) # 读取验证图像\n",
    "valid_image = valid_augment(valid_image)                                                       # 验证图像增强\n",
    "valid_label = np.array([x[1] for x in valid_data]).reshape((-1, 1)).astype(np.int64)           # 读取验证标签\n",
    "print('valid_data: image shape {}, label shape:{}'.format(valid_image.shape, valid_label.shape))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 模型设计"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "import paddle.fluid as fluid\n",
    "from paddle.fluid.dygraph.nn import Conv2D, Pool2D, Linear, BatchNorm\n",
    "import math\n",
    "\n",
    "# 模组结构：输入维度，输出维度，滑动步长，基础长度\n",
    "group_arch = [(16, 16, 1, 9), (16, 32, 2, 9), (32, 64, 2, 9)]\n",
    "group_dim  = 64 # 模组输出维度\n",
    "class_dim  = 10 # 类别数量维度\n",
    "\n",
    "# 卷积单元\n",
    "class ConvUnit(fluid.dygraph.Layer):\n",
    "    def __init__(self, in_dim, out_dim, filter_size=3, stride=1, act=None):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化卷积单元，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "            in_dim      - 输入维度\n",
    "            out_dim     - 输出维度\n",
    "            filter_size - 卷积大小\n",
    "            stride      - 滑动步长\n",
    "            act         - 激活函数\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(ConvUnit, self).__init__()\n",
    "        \n",
    "        # 添加卷积\n",
    "        self.conv = Conv2D(\n",
    "            num_channels=in_dim,\n",
    "            num_filters=out_dim,\n",
    "            filter_size=filter_size,\n",
    "            stride=stride,\n",
    "            padding=(filter_size-1)//2,                       # 输出特征图大小不变\n",
    "            param_attr=fluid.initializer.MSRA(uniform=False), # 使用MARA 初始权重\n",
    "            bias_attr=False,                                  # 卷积输出没有偏置项\n",
    "            act=None)\n",
    "        \n",
    "        # 添加正则\n",
    "        self.norm = BatchNorm(\n",
    "            num_channels=out_dim,\n",
    "            param_attr=fluid.initializer.Constant(1.0), # 使用常量初始化权重\n",
    "            bias_attr=fluid.initializer.Constant(0.0),  # 使用常量初始化偏置\n",
    "            act=act)\n",
    "    \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入的特征进行卷积和正则\n",
    "        输入:\n",
    "            x - 输入特征\n",
    "        输出:\n",
    "            x - 输出特征\n",
    "        \"\"\"\n",
    "        # 进行卷积\n",
    "        x = self.conv(x)\n",
    "        \n",
    "        # 进行正则\n",
    "        x = self.norm(x)\n",
    "        \n",
    "        return x\n",
    "\n",
    "\n",
    "# 基础结构\n",
    "class ResBasic(fluid.dygraph.Layer):\n",
    "    def __init__(self, in_dim, out_dim, stride=1, is_pass=True):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化基础结构，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "            in_dim  - 输入维度\n",
    "            out_dim - 输出维度\n",
    "            stride  - 滑动步长\n",
    "            is_pass - 是否直连\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(ResBasic, self).__init__()\n",
    "        \n",
    "        # 是否直连标识\n",
    "        self.is_pass = is_pass\n",
    "        \n",
    "        # 添加投影路径\n",
    "        self.proj = ConvUnit(in_dim=in_dim, out_dim=out_dim, filter_size=1, stride=stride, act=None)\n",
    "        \n",
    "        # 添加卷积路径\n",
    "        self.con1 = ConvUnit(in_dim=in_dim, out_dim=out_dim, filter_size=3, stride=stride, act='relu')\n",
    "        self.con2 = ConvUnit(in_dim=out_dim, out_dim=out_dim, filter_size=3, stride=1, act=None)\n",
    "        \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入的特征图像提取特征\n",
    "        输入:\n",
    "            x - 输入特征\n",
    "        输出:\n",
    "            x - 输出特征\n",
    "            y - 输出特征\n",
    "        \"\"\"\n",
    "        # 直连路径\n",
    "        if self.is_pass: # 是否直连\n",
    "            x_pass = x\n",
    "        else:            # 否则投影\n",
    "            x_pass = self.proj(x)\n",
    "        \n",
    "        # 卷积路径\n",
    "        x_con1 = self.con1(x)\n",
    "        x_con2 = self.con2(x_con1)\n",
    "        \n",
    "        # 输出特征\n",
    "        x = fluid.layers.elementwise_add(x=x_pass, y=x_con1, act='relu') # 直连路径与卷积路径进行特征相加\n",
    "        \n",
    "        return x\n",
    "    \n",
    "# 模块结构\n",
    "class ResBlock(fluid.dygraph.Layer):\n",
    "    def __init__(self, in_dim, out_dim, stride=1, basics=1):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化模块结构，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "            in_dim  - 输入维度\n",
    "            out_dim - 输出维度\n",
    "            stride  - 滑动步长\n",
    "            basics  - 基础长度\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(ResBlock, self).__init__()\n",
    "        \n",
    "        # 添加模块列表\n",
    "        self.block_list = [] # 模块列表\n",
    "        for i in range(basics):\n",
    "            block_item = self.add_sublayer( # 构造模块项目\n",
    "                'block_' + str(i),\n",
    "                ResBasic(\n",
    "                    in_dim=(in_dim if i==0 else out_dim), # 每组模块项目除第一块外，输入维度=输出维度\n",
    "                    out_dim=out_dim,\n",
    "                    stride=(stride if i==0 else 1), # 每组模块项目除第一块外，stride=1\n",
    "                    is_pass=(False if i==0 else True))) # 每组模块项目除第一块外，is_pass=True\n",
    "            self.block_list.append(block_item) # 添加模块项目\n",
    "    \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入的特征图像提取特征\n",
    "        输入:\n",
    "            x      - 输入特征\n",
    "        输出:\n",
    "            x      - 输出特征\n",
    "        \"\"\"\n",
    "        for block_item in self.block_list:\n",
    "            x = block_item(x) # 提取模块特征\n",
    "            \n",
    "        return x\n",
    "\n",
    "# 模组结构\n",
    "class ResGroup(fluid.dygraph.Layer):\n",
    "    def __init__(self):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化模组结构，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(ResGroup, self).__init__()\n",
    "        \n",
    "        # 添加模组列表\n",
    "        self.group_list = [] # 模组列表\n",
    "        for i, block_arch in enumerate(group_arch):\n",
    "            group_item = self.add_sublayer( # 构造模组项目\n",
    "                'group_' + str(i),\n",
    "                ResBlock(\n",
    "                    in_dim=block_arch[0],\n",
    "                    out_dim=block_arch[1],\n",
    "                    stride=block_arch[2],\n",
    "                    basics=block_arch[3]))\n",
    "            self.group_list.append(group_item) # 添加模组项目\n",
    "    \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入的特征图像提取特征\n",
    "        输入:\n",
    "            x      - 输入特征\n",
    "        输出:\n",
    "            x      - 输出特征\n",
    "        \"\"\"\n",
    "        for group_item in self.group_list:\n",
    "            x = group_item(x) # 提取模组特征\n",
    "            \n",
    "        return x\n",
    "        \n",
    "# 残差网络\n",
    "class ResNet(fluid.dygraph.Layer):\n",
    "    def __init__(self):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化残差网络，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(ResNet, self).__init__()\n",
    "        \n",
    "        # 添加初始化层\n",
    "        self.conv = ConvUnit(in_dim=3, out_dim=16, filter_size=3, stride=1, act='relu')\n",
    "        \n",
    "        # 添加模组结构\n",
    "        self.backbone = ResGroup() # 输出：N*C*H*W\n",
    "        \n",
    "        # 添加全连接层\n",
    "        self.pool = Pool2D(global_pooling=True, pool_type='avg') # 输出：N*C*1*1\n",
    "        \n",
    "        stdv = 1.0/(math.sqrt(group_dim)*1.0)                    # 设置均匀分布权重方差\n",
    "        self.fc = Linear(                                        # 输出：=N*10\n",
    "            input_dim=group_dim,\n",
    "            output_dim=class_dim,\n",
    "            param_attr=fluid.initializer.Uniform(-stdv, stdv),   # 使用均匀分布初始权重\n",
    "            bias_attr=fluid.initializer.Constant(0.0),           # 使用常量数值初始偏置\n",
    "            act='softmax')\n",
    "    \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入图像进行分类\n",
    "        输入:\n",
    "            x - 输入图像\n",
    "        输出:\n",
    "            x - 预测结果\n",
    "        \"\"\"\n",
    "        # 提取特征\n",
    "        x = self.conv(x)\n",
    "        x = self.backbone(x)\n",
    "        \n",
    "        # 进行预测\n",
    "        x = self.pool(x)\n",
    "        x = fluid.layers.reshape(x, [x.shape[0], -1])\n",
    "        x = self.fc(x)\n",
    "        \n",
    "        return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tatol param: 906938\n",
      "infer shape: [1, 10]\n"
     ]
    }
   ],
   "source": [
    "import paddle.fluid as fluid\n",
    "from paddle.fluid.dygraph.base import to_variable\n",
    "import numpy as np\n",
    "\n",
    "with fluid.dygraph.guard():\n",
    "    # 输入数据\n",
    "    x = np.random.randn(1, 3, 32, 32).astype(np.float32)\n",
    "    x = to_variable(x)\n",
    "    \n",
    "    # 进行预测\n",
    "    backbone = ResNet() # 设置网络\n",
    "    \n",
    "    infer = backbone(x) # 进行预测\n",
    "    \n",
    "    # 显示结果\n",
    "    parameters = 0\n",
    "    for p in backbone.parameters():\n",
    "        parameters += np.prod(p.shape) # 统计参数\n",
    "    \n",
    "    print('tatol param:', parameters)\n",
    "    print('infer shape:', infer.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 训练模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "complete - train time: 8943s, best epoch: 282, best loss: 0.268773, best accuracy: 93.01%\r"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<Figure size 432x288 with 0 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import paddle\n",
    "import paddle.fluid as fluid\n",
    "from paddle.utils.plot import Ploter\n",
    "import numpy as np\n",
    "import time\n",
    "import math\n",
    "import os\n",
    "\n",
    "epoch_num = 300   # 训练周期，取值一般为[1,300]\n",
    "train_batch = 128 # 训练批次，取值一般为[1,256]\n",
    "valid_batch = 128 # 验证批次，取值一般为[1,256]\n",
    "displays = 100    # 显示迭代\n",
    "\n",
    "start_lr = 0.00001                         # 开始学习率，取值一般为[1e-8,5e-1]\n",
    "based_lr = 0.1                             # 基础学习率，取值一般为[1e-8,5e-1]\n",
    "epoch_iters = math.ceil(50000/train_batch) # 每轮迭代数\n",
    "warmup_iter = 10 * epoch_iters             # 预热迭代数，取值一般为[1,10]\n",
    "\n",
    "momentum = 0.9     # 优化器动量\n",
    "l2_decay = 0.00005 # 正则化系数，取值一般为[1e-5,5e-4]\n",
    "epsilon = 0.05     # 标签平滑率，取值一般为[1e-2,1e-1]\n",
    "\n",
    "checkpoint = False                   # 断点标识\n",
    "model_path = './work/out/resnet'     # 模型路径\n",
    "result_txt = './work/out/result.txt' # 结果文件\n",
    "class_num  = 10                      # 类别数量\n",
    "\n",
    "with fluid.dygraph.guard():\n",
    "    # 准备数据\n",
    "    train_reader = paddle.batch(\n",
    "        reader=paddle.reader.shuffle(reader=paddle.dataset.cifar.train10(), buf_size=50000),\n",
    "        batch_size=train_batch)\n",
    "    \n",
    "    valid_reader = paddle.batch(\n",
    "        reader=paddle.dataset.cifar.test10(),\n",
    "        batch_size=valid_batch)\n",
    "    \n",
    "    # 声明模型\n",
    "    model = ResNet()\n",
    "    \n",
    "    # 优化算法\n",
    "    consine_lr = fluid.layers.cosine_decay(based_lr, epoch_iters, epoch_num) # 余弦衰减策略\n",
    "    decayed_lr = fluid.layers.linear_lr_warmup(consine_lr, warmup_iter, start_lr, based_lr) # 线性预热策略\n",
    "    \n",
    "    optimizer = fluid.optimizer.Momentum(\n",
    "        learning_rate=decayed_lr,                           # 衰减学习策略\n",
    "        momentum=momentum,                                  # 优化动量系数\n",
    "        regularization=fluid.regularizer.L2Decay(l2_decay), # 正则衰减系数\n",
    "        parameter_list=model.parameters())\n",
    "    \n",
    "    # 加载断点\n",
    "    if checkpoint: # 是否加载断点文件\n",
    "        model_dict, optimizer_dict = fluid.load_dygraph(model_path) # 加载断点参数\n",
    "        model.set_dict(model_dict)                                  # 设置权重参数\n",
    "        optimizer.set_dict(optimizer_dict)                          # 设置优化参数\n",
    "    else:          # 否则删除结果文件\n",
    "        if os.path.exists(result_txt): # 如果存在结果文件\n",
    "            os.remove(result_txt)      # 那么删除结果文件\n",
    "    \n",
    "    # 初始训练\n",
    "    avg_train_loss = 0 # 平均训练损失\n",
    "    avg_valid_loss = 0 # 平均验证损失\n",
    "    avg_valid_accu = 0 # 平均验证精度\n",
    "    \n",
    "    iterator = 1                                # 迭代次数\n",
    "    train_prompt = \"Train loss\"                 # 训练标签\n",
    "    valid_prompt = \"Valid loss\"                 # 验证标签\n",
    "    ploter = Ploter(train_prompt, valid_prompt) # 训练图像\n",
    "    \n",
    "    best_epoch = 0           # 最好周期\n",
    "    best_accu = 0            # 最好精度\n",
    "    best_loss = 100.0        # 最好损失\n",
    "    train_time = time.time() # 训练时间\n",
    "    \n",
    "    # 开始训练\n",
    "    for epoch_id in range(epoch_num):\n",
    "        # 训练模型\n",
    "        model.train() # 设置训练\n",
    "        for batch_id, train_data in enumerate(train_reader()):\n",
    "            # 读取数据\n",
    "            image_data = np.array([x[0] for x in train_data]).reshape((-1, 3, 32, 32)).astype(np.float32) # 读取图像数据\n",
    "            image_data = train_augment(image_data)                                                        # 使用数据增强\n",
    "            image = fluid.dygraph.to_variable(image_data)                                                 # 转换数据类型\n",
    "\n",
    "            label_data = np.array([x[1] for x in train_data]).astype(np.int64)                        # 读取标签数据\n",
    "            label = fluid.dygraph.to_variable(label_data)                                             # 转换数据类型\n",
    "            label = fluid.layers.label_smooth(label=fluid.one_hot(label, class_num), epsilon=epsilon) # 使用标签平滑\n",
    "            label.stop_gradient = True                                                                # 停止梯度传播\n",
    "\n",
    "            # 前向传播\n",
    "            infer = model(image)\n",
    "            \n",
    "            # 计算损失\n",
    "            loss = fluid.layers.cross_entropy(infer, label, soft_label=True)\n",
    "            train_loss = fluid.layers.mean(loss)\n",
    "            \n",
    "            # 反向传播\n",
    "            train_loss.backward()\n",
    "            optimizer.minimize(train_loss)\n",
    "            model.clear_gradients()\n",
    "            \n",
    "            # 显示结果\n",
    "            if iterator % displays == 0:\n",
    "                # 显示图像\n",
    "                avg_train_loss = train_loss.numpy()[0]                # 设置训练损失\n",
    "                ploter.append(train_prompt, iterator, avg_train_loss) # 添加训练图像\n",
    "                ploter.plot()                                         # 显示训练图像\n",
    "                \n",
    "                # 打印结果\n",
    "                print(\"iteration: {:6d}, epoch: {:3d}, train loss: {:.6f}, valid loss: {:.6f}, valid accuracy: {:.2%}\".format(\n",
    "                    iterator, epoch_id+1, avg_train_loss, avg_valid_loss, avg_valid_accu))\n",
    "                \n",
    "                # 写入文件\n",
    "                with open(result_txt, 'a') as file:\n",
    "                    file.write(\"iteration: {:6d}, epoch: {:3d}, train loss: {:.6f}, valid loss: {:.6f}, valid accuracy: {:.2%}\\n\".format(\n",
    "                        iterator, epoch_id+1, avg_train_loss, avg_valid_loss, avg_valid_accu))\n",
    "            \n",
    "            # 增加迭代\n",
    "            iterator += 1\n",
    "            \n",
    "        # 验证模型\n",
    "        valid_loss_list = [] # 验证损失列表\n",
    "        valid_accu_list = [] # 验证精度列表\n",
    "        \n",
    "        model.eval()   # 设置验证\n",
    "        for batch_id, valid_data in enumerate(valid_reader()):\n",
    "            # 读取数据\n",
    "            image_data = np.array([x[0] for x in valid_data]).reshape((-1, 3, 32, 32)).astype(np.float32) # 读取图像数据\n",
    "            image_data = valid_augment(image_data)                                                        # 使用图像增强\n",
    "            image = fluid.dygraph.to_variable(image_data)                                                 # 转换数据类型\n",
    "            \n",
    "            label_data = np.array([x[1] for x in valid_data]).reshape((-1, 1)).astype(np.int64) # 读取标签数据\n",
    "            label = fluid.dygraph.to_variable(label_data)                                       # 转换数据类型\n",
    "            label.stop_gradient = True                                                          # 停止梯度传播\n",
    "            \n",
    "            # 前向传播\n",
    "            infer = model(image)\n",
    "            \n",
    "            # 计算精度\n",
    "            valid_accu = fluid.layers.accuracy(infer,label)\n",
    "            \n",
    "            valid_accu_list.append(valid_accu.numpy())\n",
    "            \n",
    "            # 计算损失\n",
    "            loss = fluid.layers.cross_entropy(infer, label)\n",
    "            valid_loss = fluid.layers.mean(loss)\n",
    "            \n",
    "            valid_loss_list.append(valid_loss.numpy())\n",
    "        \n",
    "        # 设置结果\n",
    "        avg_valid_accu = np.mean(valid_accu_list)             # 设置验证精度\n",
    "        \n",
    "        avg_valid_loss = np.mean(valid_loss_list)             # 设置验证损失\n",
    "        ploter.append(valid_prompt, iterator, avg_valid_loss) # 添加训练图像\n",
    "        \n",
    "        # 保存模型\n",
    "        fluid.save_dygraph(model.state_dict(), model_path)     # 保存权重参数\n",
    "        fluid.save_dygraph(optimizer.state_dict(), model_path) # 保存优化参数\n",
    "        \n",
    "        if avg_valid_loss < best_loss:\n",
    "            fluid.save_dygraph(model.state_dict(), model_path + '-best') # 保存权重\n",
    "            \n",
    "            best_epoch = epoch_id + 1                                    # 更新迭代\n",
    "            best_accu = avg_valid_accu                                   # 更新精度\n",
    "            best_loss = avg_valid_loss                                   # 更新损失\n",
    "    \n",
    "    # 显示结果\n",
    "    train_time = time.time() - train_time # 设置训练时间\n",
    "    print('complete - train time: {:.0f}s, best epoch: {:3d}, best loss: {:.6f}, best accuracy: {:.2%}'.format(\n",
    "        train_time, best_epoch, best_loss, best_accu))\n",
    "    \n",
    "    # 写入文件\n",
    "    with open(result_txt, 'a') as file:\n",
    "        file.write('complete - train time: {:.0f}s, best epoch: {:3d}, best loss: {:.6f}, best accuracy: {:.2%}\\n'.format(\n",
    "            train_time, best_epoch, best_loss, best_accu))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 模型预测"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "infer time: 0.020971s, infer value: horse\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 216x216 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import paddle.fluid as fluid\n",
    "from PIL import Image\n",
    "import numpy as np\n",
    "import time\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "image_path = './work/out/img.png' # 图片路径\n",
    "model_path = './work/out/resnet-best' # 模型路径\n",
    "\n",
    "# 加载图像\n",
    "def load_image(image_path):\n",
    "    \"\"\"\n",
    "    功能:\n",
    "        读取图像并转换到输入格式\n",
    "    输入:\n",
    "        image_path - 输入图像路径\n",
    "    输出:\n",
    "        image - 输出图像\n",
    "    \"\"\"\n",
    "    # 读取图像\n",
    "    image = Image.open(image_path) # 打开图像文件\n",
    "    \n",
    "    # 转换格式\n",
    "    image = image.resize((32, 32), Image.ANTIALIAS) # 调整图像大小\n",
    "    image = np.array(image, dtype=np.float32) # 转换数据格式，数据类型转换为float32\n",
    "\n",
    "    # 减去均值\n",
    "    mean = np.array([0.4914, 0.4822, 0.4465]).reshape((1, 1, -1)) # cifar数据集通道平均值\n",
    "    stdv = np.array([0.2471, 0.2435, 0.2616]).reshape((1, 1, -1)) # cifar数据集通道标准差\n",
    "    \n",
    "    image = (image/255.0 - mean) / stdv # 对图像进行归一化\n",
    "    image = image.transpose((2, 0, 1)).astype(np.float32) # 数据格式从HWC转换为CHW，数据类型转换为float32\n",
    "    \n",
    "    # 增加维度\n",
    "    image = np.expand_dims(image, axis=0) # 增加数据维度\n",
    "    \n",
    "    return image\n",
    "\n",
    "# 预测图像\n",
    "with fluid.dygraph.guard():\n",
    "    # 读取图像\n",
    "    image = load_image(image_path)\n",
    "    image = fluid.dygraph.to_variable(image)\n",
    "    \n",
    "    # 加载模型\n",
    "    model = ResNet()                               # 加载模型\n",
    "    model_dict, _ = fluid.load_dygraph(model_path) # 加载权重\n",
    "    model.set_dict(model_dict)                     # 设置权重\n",
    "    model.eval()                                   # 设置验证\n",
    "    \n",
    "    # 前向传播\n",
    "    infer_time = time.time()              # 推断开始时间\n",
    "    infer = model(image)\n",
    "    infer_time = time.time() - infer_time # 推断结束时间\n",
    "    \n",
    "    # 显示结果\n",
    "    vlist = [\"airplane\", \"automobile\", \"bird\", \"cat\", \"deer\", \"dog\", \"frog\", \"horse\", \"ship\", \"truck\"] # 标签名称列表\n",
    "    print('infer time: {:f}s, infer value: {}'.format(infer_time, vlist[np.argmax(infer.numpy())]) )\n",
    "    \n",
    "    image = Image.open(image_path) # 打开图像文件\n",
    "    plt.figure(figsize=(3, 3))     # 设置显示大小\n",
    "    plt.imshow(image)              # 设置显示图像\n",
    "    plt.show()                     # 显示图像文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
